I was looking through ways to add a custom step of sms otp. Came across this https://github.com/UKGovernmentBEIS/keycloak-sms-authenticator-sns This works fine in version 7.0.1 ok keycloak. Then i tested it in version 8. where it breaks at two point. Initial being the options for an authentication step which were (optional, alternative, required) Which are different in two step. So i modified the code to make it work. Then it compiles and you get the option of adding sms authentication step. Now when you again run it it breaks at another point which involves creating the UserCredentialModel object.
Please take in consideration the below code
Version 7 UserCredentialModel.java file
package org.keycloak.models;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialModel;
import org.keycloak.models.credential.PasswordUserCredentialModel;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class UserCredentialModel implements CredentialInput {
public static final String PASSWORD = CredentialModel.PASSWORD;
public static final String PASSWORD_HISTORY = CredentialModel.PASSWORD_HISTORY;
public static final String PASSWORD_TOKEN = CredentialModel.PASSWORD_TOKEN;
// Secret is same as password but it is not hashed
public static final String SECRET = CredentialModel.SECRET;
public static final String TOTP = CredentialModel.TOTP;
public static final String HOTP = CredentialModel.HOTP;
public static final String CLIENT_CERT = CredentialModel.CLIENT_CERT;
public static final String KERBEROS = CredentialModel.KERBEROS;
protected String type;
protected String value;
protected String device;
protected String algorithm;
// Additional context informations
protected Map<String, Object> notes = new HashMap<>();
public UserCredentialModel() {
}
public static PasswordUserCredentialModel password(String password) {
return password(password, false);
}
public static PasswordUserCredentialModel password(String password, boolean adminRequest) {
PasswordUserCredentialModel model = new PasswordUserCredentialModel();
model.setType(PASSWORD);
model.setValue(password);
model.setAdminRequest(adminRequest);
return model;
}
public static UserCredentialModel passwordToken(String passwordToken) {
UserCredentialModel model = new UserCredentialModel();
model.setType(PASSWORD_TOKEN);
model.setValue(passwordToken);
return model;
}
public static UserCredentialModel secret(String password) {
UserCredentialModel model = new UserCredentialModel();
model.setType(SECRET);
model.setValue(password);
return model;
}
public static UserCredentialModel otp(String type, String key) {
if (type.equals(HOTP)) return hotp(key);
if (type.equals(TOTP)) return totp(key);
throw new RuntimeException("Unknown OTP type");
}
public static UserCredentialModel totp(String key) {
UserCredentialModel model = new UserCredentialModel();
model.setType(TOTP);
model.setValue(key);
return model;
public static UserCredentialModel kerberos(String token) {
UserCredentialModel model = new UserCredentialModel();
model.setType(KERBEROS);
model.setValue(token);
return model;
}
public static UserCredentialModel generateSecret() {
UserCredentialModel model = new UserCredentialModel();
model.setType(SECRET);
model.setValue(UUID.randomUUID().toString());
return model;
}
public static boolean isOtp(String type) {
return TOTP.equals(type) || HOTP.equals(type);
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getDevice() {
return device;
}
In this there is a defination of UserCredential model which incorporates OTP style auth
Now if we look at the UserCredentialModel defined in version 8 of key cloak
package org.keycloak.models;
import org.keycloak.credential.CredentialInput;
import org.keycloak.credential.CredentialModel;
import org.keycloak.models.credential.OTPCredentialModel;
import org.keycloak.models.credential.PasswordCredentialModel;
import java.util.UUID;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class UserCredentialModel implements CredentialInput {
@Deprecated /** Use PasswordCredentialModel.TYPE instead **/
public static final String PASSWORD = PasswordCredentialModel.TYPE;
@Deprecated /** Use PasswordCredentialModel.PASSWORD_HISTORY instead **/
public static final String PASSWORD_HISTORY = PasswordCredentialModel.PASSWORD_HISTORY;
@Deprecated /** Use OTPCredentialModel.TOTP instead **/
public static final String TOTP = OTPCredentialModel.TOTP;
@Deprecated /** Use OTPCredentialModel.TOTP instead **/
public static final String HOTP = OTPCredentialModel.HOTP;
public static final String SECRET = CredentialModel.SECRET;
public static final String KERBEROS = CredentialModel.KERBEROS;
public static final String CLIENT_CERT = CredentialModel.CLIENT_CERT;
private final String credentialId;
private final String type;
private final String challengeResponse;
private final boolean adminRequest;
public UserCredentialModel(String credentialId, String type, String challengeResponse) {
this.credentialId = credentialId;
this.type = type;
this.challengeResponse = challengeResponse;
this.adminRequest = false;
}
public UserCredentialModel(String credentialId, String type, String challengeResponse, boolean adminRequest) {
this.credentialId = credentialId;
this.type = type;
this.challengeResponse = challengeResponse;
this.adminRequest = adminRequest;
}
public static UserCredentialModel password(String password) {
return password(password, false);
}
public static UserCredentialModel password(String password, boolean adminRequest) {
return new UserCredentialModel("", PasswordCredentialModel.TYPE, password, adminRequest);
}
public static UserCredentialModel secret(String password) {
return new UserCredentialModel("", SECRET, password);
}
public static UserCredentialModel kerberos(String token) {
return new UserCredentialModel("", KERBEROS, token);
}
public static UserCredentialModel generateSecret() {
return new UserCredentialModel("", SECRET, UUID.randomUUID().toString());
}
@Override
public String getCredentialId() {
return credentialId;
}
@Override
public String getType() {
return type;
}
@Override
public String getChallengeResponse() {
return challengeResponse;
}
public boolean isAdminRequest() {
return adminRequest;
}
}
There is no defination which involves otp style auth.
How can one add OTP style authentication object factory so that we can write a custom sms authenticator for version 8
The error thrown by server log is
15:21:18,079 WARN [org.keycloak.events] (default task-3) type=LOGIN_ERROR, realmId=test3, clientId=null, userId=null, ipAddress=127.0.0.1, error=expired_code, restart_after_timeout=true, authSessionParentId=e345cefe-12fd-4711-b056-ae39c0357e40, authSessionTabId=yaoCyto_xUU
15:21:27,140 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-3) Uncaught server error: java.lang.NoSuchMethodError: org.keycloak.models.UserCredentialModel: method ()V not found
at six.six.keycloak.authenticator.KeycloakSmsAuthenticator.storeSMSCode(KeycloakSmsAuthenticator.java:170)
at six.six.keycloak.authenticator.KeycloakSmsAuthenticator.authenticate(KeycloakSmsAuthenticator.java:81)
at org.keycloak.authentication.DefaultAuthenticationFlow.processSingleFlowExecutionModel(DefaultAuthenticationFlow.java:453)
at org.keycloak.authentication.DefaultAuthenticationFlow.processFlow(DefaultAuthenticationFlow.java:305)
at org.keycloak.authentication.DefaultAuthenticationFlow.processAction(DefaultAuthenticationFlow.java:185)
at org.keycloak.authentication.AuthenticationProcessor.authenticationAction(AuthenticationProcessor.java:958)
at org.keycloak.services.resources.LoginActionsService.processFlow(LoginActionsService.java:294)
at org.keycloak.services.resources.LoginActionsService.processAuthentication(LoginActionsService.java:265)
at org.keycloak.services.resources.LoginActionsService.authenticate(LoginActionsService.java:261)
at org.keycloak.services.resources.LoginActionsService.authenticateForm(LoginActionsService.java:322)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)