Hi team,
I’m trying (for 3 days already) to find a solution to my scenario:
- My users are identified using a number (CPF, it’s like a social security number in brazil). So the username is something like 12345678901
- They also have the email address, everyone uses the same domain @iftm.edu.br (we are a public school)
- The realm is configured to allow login with username or email
The users need to be able to login using only the first part of the email, without needing to fill the complete address on the input. So a user with CPF = 12345678901 and email carlos@iftm.edu.br must be able to login using just ‘carlos’ as username.
I found that I could implement an SPI to build a custom authenticator, and append the ‘@iftm.edu.br’ part to the input value. So i built the jar, deployed on my Keycloak and set-up the flow, replacing the form with my SPI.
It works when the users try to login using CPF or email (I’ve added log messages to check if this cases are being ignored).
When the user tries to login using the first part of the email address, the SPI do append the domain to it, but it seems that it keeps using the original value, and the login fails.
I don’t know what else to do, so I’m asking for help.
Thank you in advance =)
the code:
package org.keycloak.auth;
import org.jboss.logging.Logger;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.authenticators.browser.UsernamePasswordForm;
import jakarta.ws.rs.core.MultivaluedMap;
public class UsernameAutoCompleteAuthenticator extends UsernamePasswordForm implements Authenticator {
private static final Logger logger = Logger.getLogger(UsernameAutoCompleteAuthenticator.class);
private static final String EMAIL_DOMAIN = "@iftm.edu.br";
@Override
public void authenticate(AuthenticationFlowContext context) {
super.authenticate(context);
}
@Override
public void action(AuthenticationFlowContext context) {
logger.info("*** AUTOCOMPLETE *** itworks");
// Retrieve the username from the form data
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
String username = formData.getFirst("username");
if (username != null) {
if (username.matches("\\d{11}")) {
// Username is a sequence of 11 numbers, no modification needed
logger.warnf(" *** AUTOCOMPLETE *** Username is a CPF, no modification needed: %s", username);
} else if (!username.contains("@")) {
// Append the email domain if not already present
username += EMAIL_DOMAIN;
logger.infof(" *** AUTOCOMPLETE *** Updated username with domain: %s", username);
// None of this seems to work
formData.putSingle("username", username);
context.getAuthenticationSession().setAuthNote("username", username);
context.getSession().setAttribute("username", username);
} else {
logger.warnf(" *** AUTOCOMPLETE *** Username already contains '@', no modification needed: %s", username);
}
}
// Call the default UsernamePasswordForm behavior
super.action(context);
// Ensure the flow status is set
if (context.getStatus() == null) {
logger.warn("Flow status is null. Setting failure status.");
context.failure(AuthenticationFlowError.INTERNAL_ERROR);
}
logger.debug("*** AUTOCOMPLETE *** Exiting action method.");
}
@Override public boolean requiresUser() { return false; }
@Override public boolean configuredFor(org.keycloak.models.KeycloakSession session, org.keycloak.models.RealmModel realm, org.keycloak.models.UserModel user) { return true; }
@Override public void setRequiredActions(org.keycloak.models.KeycloakSession session, org.keycloak.models.RealmModel realm, org.keycloak.models.UserModel user) {}
@Override public void close() {}
}
the flow:
the console output: