Keycloak TokenExchange flow (Internal token to external token exchange)

Hello,

I’m testing the TokenExchange flow implementation with Keycloak. I’m using the documentation Using token exchange - Keycloak

This is my environment:

  • 1 “custom-auth” client used to request token exchange

  • 1 identity provider “github” with “Permissions enabled” set to “On”.

    • Configuring the token-exchange permission:
    • Configuring the associated policy: positive + 2 clients: “custom-auth” and “realm-management”

I. When I succeed to use the internal to external function

  1. the user doesn’t exist
  2. I connect to http://localhost:8080/realms/dev/account with the provider (github)
  3. I retrieve the token with F12
  4. execute the token-exchange request:
POST - http://localhost:8080/realms/dev/protocol/openid-connect/token

BODY (x-www-form-urlencoded)
client_id = custom-auth
grant_type = urn:ietf:params:oauth:grant-type:token-exchange
subject_token = [F12 token]
requested_token_type = urn:ietf:params:oauth:token-type:access_token
requested_issuer = github
  1. I get a valid response:
{
  "access_token": "gho_[...]",
  "expires_in": 0,
  "refresh_expires_in": 0,
  "not-before-policy": 0,
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "account-link-url": "http://localhost:8080/realms/dev/broker/github/link?nonce=[...]&hash=[...]&client_id=custom-auth"
}

II. Case that doesn’t work (1)

  1. With the same user (from I.), I add a password via the “Credentials” tab
  2. I retrieve a token:
curl -d client_id=custom-auth -d username=killian -d password=azerty -d grant_type=password http://localhost:8080/realms/dev/protocol/openid-connect/token
  1. execute the token-exchange request
POST - http://localhost:8080/realms/dev/protocol/openid-connect/token

BODY (x-www-form-urlencoded)
client_id = custom-auth
grant_type = urn:ietf:params:oauth:grant-type:token-exchange
subject_token = [curl's token]
requested_token_type = urn:ietf:params:oauth:token-type:access_token
requested_issuer = github
  1. I get an error (not normal in my opinion):
{
    "error_description": "identity provider is not linked, can only link to current user session",
    "account-link-url": "http://localhost:8080/realms/dev/broker/github/link?nonce=...&hash=...&client_id=custom-auth",
    "error": "not_linked"
}

The doc says “The user must have logged in with the external identity provider at least once”. However,
I have the feeling that the only tokens that work are those generated from a connection via the provider.
If this is the case, how can I get this access token with curl?

III. Case that doesn’t work (2)

  1. I create a user with a password, but it’s not linked to a github account
  2. I get a token:
curl -d client_id=custom-auth -d username=killian -d password=azerty -d grant_type=password http://localhost:8080/realms/dev/protocol/openid-connect/token
  1. execute the token-exchange request:
POST - http://localhost:8080/realms/dev/protocol/openid-connect/token

BODY (x-www-form-urlencoded)
client_id = custom-auth
grant_type = urn:ietf:params:oauth:grant-type:token-exchange
subject_token = [curl's token]
requested_token_type = urn:ietf:params:oauth:token-type:access_token
requested_issuer = github
  1. I get an error (normal):
{
    "error_description": "identity provider is not linked, can only link to current user session",
    "account-link-url": "http://localhost:8080/realms/dev/broker/github/link?nonce=...&hash=...&client_id=custom-auth",
    "error": "not_linked"
}
  1. I then execute the following query (adding redirect_uri as indicated in the doc):
GET - http://localhost:8080/realms/dev/broker/github/link?nonce=...&hash=...&client_id=custom-auth&redirect_uri=http://localhost:8080/realms/dev/account
  1. I am redirected without error (code 200), but the account is not linked, I was never redirected to github

IV. Questions

Am I doing something wrong?
I have the feeling that the “Client initiated account linking” API isn’t working properly => Server Developer Guide

To sum up, does the “account-link-url” work as I understand it and why is it only the tokens from http://localhost:8080/realms/dev/account/ (client acount-console) that work?

Thanks in advance for your answers!