I\'ve seen those two post that give a solution to this question but they do not provide detailed enough informations about how to do it for non Java developer like me:
K
I am using a Custom Protocol Mapper1 to send an authenticated2 GraphQL query3 to an external system and put the JSON response data into the user's access token (JWT). It currently runs with Keycloak 10.
==> You can find the full code in this repository.
As others have noted, your project needs at least 3 files.
AbstractOIDCProtocolMapper
& its method setClaim
(among others).jboss-deployment-structure.xml
file that contains the dependencies for deployment.org.keycloak.protocol.ProtocolMapper
file that contains the full name of the custom protocol mapper.Here is the folder structure:
$ tree src/ -A
src/
└── main
├── java
│ └── com
│ └── thohol
│ └── keycloak
│ └── JsonGraphQlRemoteClaim.java
└── resources
└── META-INF
├── jboss-deployment-structure.xml
└── services
└── org.keycloak.protocol.ProtocolMapper
If the remote endpoint requires authentication, we can obtain an Access Token from Keycloak. The complete flow would look as follows (especially steps 3-6):
login-client
.login-client
is configured to use the Custom Protocol Mapper, its code gets executed while the user's authentication request is being processed.remote-claims-client
) using client_credentials
(Client ID + Secret).remote-claims-client
.Authorization: Bearer
header is added to the request headers.remote-claims-client
.Steps 3/4 can be implemented as a simple HTTP POST request in Java (error handling omitted!):
// Call remote service
HttpClient httpClient = HttpClient.newHttpClient();
URIBuilder uriBuilder = new URIBuilder(keycloakAuthUrl);
URI uri = uriBuilder.build();
HttpRequest.Builder builder = HttpRequest.newBuilder().uri(uri);
String queryBody = "grant_type=client_credentials&client_id=remote-claims-client&client_secret=dfebc62a-e8d7-4ab3-9196-258ddb5684ab";
builder.POST(HttpRequest.BodyPublishers.ofString(queryBody));
// Build headers
builder.header(HttpHeaders.CONTENT_TYPE , MediaType.APPLICATION_FORM_URLENCODED);
// Call
HttpResponse response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString());
// Process Response
JsonNode json = return new ObjectMapper().readTree(response.body());
String accessToken = json.findValue("access_token").asText();
A GraphQL query is essentially an HTTP POST request, with a body
like
{
"query": "query HeroName($episode: Episode) {
hero(episode: $episode) {
name
}
}",
"variables": {
"episode" : "JEDI"
}
}
This can be sent from Java like (error handling omitted!):
HttpClient httpClient = HttpClient.newHttpClient();
URIBuilder uriBuilder = new URIBuilder(remoteUrl);
URI uri = uriBuilder.build();
HttpRequest.Builder builder = HttpRequest.newBuilder().uri(uri);
String queryBody = "{
\"query\": \"query HeroName($episode: Episode) {
hero(episode: $episode) {
name
}
}\",
\"variables\": {
\"episode\" : \"JEDI\"
}
}";
builder.POST(HttpRequest.BodyPublishers.ofString(queryBody));
// Build headers
builder.header(HttpHeaders.CONTENT_TYPE , MediaType.APPLICATION_JSON);
builder.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
// Call
HttpResponse response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString());
// Process Response and add to token
JsonNode json = return new ObjectMapper().readTree(response.body());
clientSessionCtx.setAttribute("custom_claims", json);
I am the owner/author of the linked repository. However, I did not start from scratch but used multiple other repositories as basis/inspiration. See the repo's README.