keycloakopenidtoken-exchange

How to add specific audiences to a token for a token exchange in Keycloak?


I want to implement token exchange between two internal clients, but I keep getting the error that the audience is not within scope. I'm in v24. I have client A, client B, and client X. Client X has full scope allowed so in the token audience the other two clients appear, so I'm able to successfully exchange a client X token (obtained via oauth2) for a client A or client B token, using the request below.

return this.client.grant({
    grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
    client_id: client (A or B) id,
    client_secret: client (A or B) secret,
    subject_token: client Z accessToken,
    subject_token_type: "urn:ietf:params:oauth:token-type:access_token",
    requested_token_type: "urn:ietf:params:oauth:token-type:refresh_token",
    scope: "openid email profile"
});

The access token I get from client A or B only contain themselves as audiences, and I can't exchange one for the other. I'm trying to exchange client A's access token for a client B access token. I tried to configure the audience in the token via a an audience mapper and also tried the official documentation steps, but this documentation seems outdated, a lot of the steps are not clear or seem incomplete.

Most of the info I found in StackOverflow is about impersonation which is not what I need, and in the Keycloak Slack my questions go usually not replied. Even documentation that seemed relevant is returning a 404.

I've tried adding my special scope to the grant request, it doesn't change anything. I tried adding the "audience" parameter to my grant request, but this only works for me as a string, so even though the RFC documentation says it should accept an array, I haven't managed to pass one successfully (I get Bad Request: OPError: invalid_request (duplicated parameter when I try). I have tried using the client_id and secret of the origin client instead of the target client, but this doesn't seem to work, I just get a token from the origin client, not the target one.

So, I have many many questions:

I would like to be able to do the token exchange without having to grant full scope to clients A and B, but I don't know what I'm missing. Any pointers to token exchange between internal clients code examples would be very welcome!


Solution

  • So what ended up working is:

    1. Client scope:
      enter image description here

    2. Add Audience mappers for each client:
      enter image description hereenter image description here(Obviously in Included Client Audience, add a real existing client)
      So, supposing we have potato-client-1, potato-client-2, potato-client-3, we would create Audience mappers for all 3 and add them to the scope we created earlier. The list below would have 3 mappers in our scope.
      enter image description here

    3. Once the scope is set, go to the Clients > Select your relevant client >Client Scopes tab and add the scope just created to each one of the clients (potato-client-1, 2 and 3).

    4. On your code, you should now be able to exchange tokens between the clients, passing the scope you just created. Please note that the client ID and secret should be the ones of the target client, so, if your currentToken is from potato-client-1 and you want to exchange it for a token for potato-client-3, the client ID and secret need to be for potato-client-3

      return this.client.grant({
          grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
          client_id: config.clientId,
          client_secret: config.clientSecret,
          subject_token: currentToken,
          subject_token_type: "urn:ietf:params:oauth:token-type:access_token",
          requested_token_type: "urn:ietf:params:oauth:token-type:refresh_token",
          scope: "openid potato-audience-mapping"
      });
      

    PS: I don't remember properly now, but I think after doing this I had an issue since the refresh tokens can still only be requested in behalf of the azp (Authorized Parties) in the token, and there was a bug in KeyCloak not setting the correct azp in the tokens after an exchange (https://github.com/keycloak/keycloak/issues/8756). I don't think this has been fixed, so this means I can only use refresh tokens for one client and need to do an exchange for the necessary client afterwards every time, which makes for very ugly code and unnecessary calls. So if anyone has a good solution for this, please do let me know.