I need to implement a relatively complex authorization process for a Spring Boot application and consider using Keycloak for this.
Is it possible to do following things using Keycloak (incl. by extending it with custom authentication/authorization mechanisms)?
Keeping track of sessions: Does Keycloak know on how many devices a user is logged in into the application?
Keeping track of failed login attempts: Does Keycloak know how many times a particular user entered an incorrect password?
Terminating sessions: Is it possible to terminate some (but not all) sessions of a user?
My answers
1. Keeping track of session
According to the user manual, section "Managing user sessions", "Viewing client sessions" it is possible to see all active sessions of a user via the web UI.
According to this answer, it is possible to do so programmatically.
2. Keeping track of failed login attempts
According to this page, it may be possible to implement it using a custom event listener.
3. Terminating sessions
It looks like it is possible using the http://auth-server{kc_realms_path}/{realm-name}/protocol/openid-connect/logout endpoint according to documentation and this answer.
Update 1: It looks like items 1 and 2 are indeed possible.
However, I am having trouble with termination of sessions.
This is what I want:
- User logs in via Keycloak into a Spring Boot application.
- When I terminate the session, the user is logged out of that application.
First, I tried to delete sessions using code like this:
final Keycloak keycloak = KeycloakBuilder.builder()
.serverUrl("http://localhost:8080")
.realm("KeycloakDemo")
.username("postman")
.password("postman2022")
.clientId("postman")
.clientSecret("ZY006ddQbWHdSiAK3A06rrPlKgSz3XS0")
.build();
final UserRepresentation user =
keycloak.realm("KeycloakDemo").users().search("user1").get(0);
final String userId = user.getId();
final UserSessionRepresentation[] sessions = keycloak
.realm("KeycloakDemo")
.users().get(userId).getUserSessions()
.toArray(new UserSessionRepresentation[0]);
if (sessions.length > 0) {
final String sessionId = sessions[0].getId();
keycloak.realm("KeycloakDemo").deleteSession(sessionId);
}
This piece of code deletes sessions (i. e. they are not visible in the Keycloak GUI any longer), but it does not log out the user.
Another attempt was to log out the user after the session was deleted using the following code.
final String token = getToken();
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("text/plain");
RequestBody body = RequestBody.create(mediaType, "");
Request request = new Request.Builder()
.url("http://localhost:8080/realms/KeycloakDemo/protocol/openid-connect/logout?id_token_hint=" + token)
.method("GET", null)
.build();
Response response = client.newCall(request).execute();
getToken() is defined as follows:
private String getToken() throws IOException {
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
RequestBody body = RequestBody.create(mediaType, "client_id=admin-cli&username=postman&password=postman2022&grant_type=client_credentials&scope=openid&realm=KeycloakDemo&client_secret=CMewUzBUsiy0gUqg6uEmCRBgR5p6f5Nu");
Request request = new Request.Builder()
.url("http://localhost:8080/realms/KeycloakDemo/protocol/openid-connect/token")
.method("POST", body)
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.addHeader("Authorization", "bearer ... ")
.build();
Response response = client.newCall(request).execute();
if (response.code() != 200) {
System.exit(1);
}
final ObjectMapper om = new ObjectMapper();
final JsonNode jsonNode = om.readTree(response.body().string());
return jsonNode.get("id_token").asText();
}
This does not work, either (the user stays logged in that application, ever if I refresh the page in the browser).