I need to restrict access to some clients in keycloak based on roles. So, if a user wants to login to the restricted client A, the user needs to have the client role A.
If the user wants to login to client B, which is not a restricted client, the user should be able to login.
I don’t want to use Authorization. So, my current solution looks like this:
- If a user should be allowed to login to a restricted client, the user has to have the specific client role (assuming that a restricted client has such a client role).
- I implemented a custom authentication step in Java code. This code checks if the user has the role for the restricted client, see my Authenticator code at the end of the question.
- I added the custom step to the browser flow:
Result:
The user cannot login to the restricted client if the user does not have the required role - IF the user does not have a current session.
Problem:
If the user has already successfully logged in to client B (which is not restricted), the user is automatically logged in to client A as well - because of the cookie authentication. So, my custom authentication step is not executed if cookie authentication was already successful.
I could find an explanation to this behavior in the keycloak documentation, but no solution: “Since the cookie provider returned success and each execution at this level of the flow is alternative, Keycloak does not perform any other exections.”
Does anyone have a solution to this?
Java Custom Authentication Step:
public class ClientRoleAuthenticator implements Authenticator {
private static final Logger logger = Logger.getLogger(ClientRoleAuthenticator.class);
@Override
public void authenticate(AuthenticationFlowContext context) {
AuthenticationSessionModel authenticationSession = context.getAuthenticationSession();
ClientModel client = authenticationSession.getClient();
if (client == null) {
logger.error("Client is null.");
context.failure(AuthenticationFlowError.INTERNAL_ERROR);
return;
}
String clientId = client.getClientId();
logger.info("clientId: " + clientId);
RoleModel role = client.getRole(clientId);
if (role == null || !clientHasSameNameRole(client, role)) {
// Skip authentication step if client has no role of the same name
logger.info("Client has no role of the same name. Continuing...");
context.success();
return;
}
UserModel user = authenticationSession.getAuthenticatedUser();
if (user == null) {
// Skip authentication step if no user context
logger.info("No user context. Continuing...");
context.success();
return;
}
if (user.hasRole(role)) {
// Continue authentication flow if user has the same name role
logger.info("User has the same name role. Continuing...");
context.setUser(user);
context.success();
} else {
// Intercept the authentication flow if user does not have the same name role
logger.error("User does not have the same name role. Access denied.");
context.failure(AuthenticationFlowError.ACCESS_DENIED);
}
}
private boolean clientHasSameNameRole(ClientModel client, RoleModel role) {
return role != null && role.getName().equals(client.getClientId());
}
@Override
public void action(AuthenticationFlowContext authenticationFlowContext) {
}
@Override
public boolean requiresUser() {
return false;
}
@Override
public boolean configuredFor(KeycloakSession keycloakSession, RealmModel realmModel, UserModel userModel) {
return false;
}
@Override
public void setRequiredActions(KeycloakSession keycloakSession, RealmModel realmModel, UserModel userModel) {
}
@Override
public void close() {
}
}