Recaptcha authenticator that works with IDP

Hi All,

I have a Keycloak 23.0.4 instance where I have created a user federation plugin that would allow me to make an API call to log into my application. I would like to create an authenticator that would add in recaptcha v3 to the login page to block bots from spamming my log in. I’ve tried this plugin to add the recaptcha, but it appears that the IDP interrupts its flow somewhere and the recaptcha doesn’t try the validate the recaptcha response after an action is triggered. Has anyone else attempted to do this or have any advice on how i can get this to work?

Hi Bryce,
are you using our compose.yaml example? or the jar file in your project?
Make sure you initialize the configuration for the authenticator in the authentication flow.
In case you still have an error, feel free to open an issue in our repo, sharing the log.

Regards, Gustavo

Hi Gustavo,

I am using the jar in my project and I have followed the configuration steps from your repo’s README page. I was wondering if this has been used with a user storage plugin before, since my user storage plugin seems to be interfering with the authentication flow. When the User storage plugin fails to login due to a bad password, the Recaptcha authentiction flow doesn’t trigger again:

2024-02-27 14:03:27,003 INFO  [de.sventorben.keycloak.authentication.hidpd.HomeIdpDiscoverer] (executor-thread-70) Could not find home IdP for domain 'gmail.com' and user 'comqitesting@gmail.com' in realm 'dap-ngrt'
2024-02-27 14:03:27,005 INFO  [com.comqi.ngrt.recaptcha.RecaptchaLoginFormFactory] (executor-thread-70) RestHandler already instantiated
2024-02-27 14:03:27,005 INFO  [com.comqi.ngrt.recaptcha.RecaptchaLoginForm] (executor-thread-70) Starting authentication flow
2024-02-27 14:03:27,006 INFO  [com.comqi.ngrt.recaptcha.RecaptchaLoginForm] (executor-thread-70) Verifying recaptcha configuration
2024-02-27 14:03:27,007 INFO  [com.comqi.ngrt.recaptcha.RecaptchaLoginForm] (executor-thread-70) Recaptcha configuration is available
2024-02-27 14:03:33,690 INFO  [com.comqi.ngrt.recaptcha.RecaptchaLoginForm] (executor-thread-67) Validating recaptcha response
2024-02-27 14:03:33,691 INFO  [com.comqi.ngrt.recaptcha.RestHandler] (executor-thread-67) Executing request to https://www.google.com/recaptcha/api/siteverify
2024-02-27 14:03:33,803 INFO  [com.comqi.ngrt.recaptcha.RestHandler] (executor-thread-67) Recaptcha JSON response: {success=true, challenge_ts=2024-02-27T14:03:28Z, hostname=keycloak-sandbox.comqilabs.com, score=0.3, action=submit}
2024-02-27 14:03:33,803 INFO  [com.comqi.ngrt.recaptcha.RestHandler] (executor-thread-67) Val: true
2024-02-27 14:03:33,803 INFO  [com.comqi.ngrt.recaptcha.RestHandler] (executor-thread-67) Recaptcha validation successful: true
2024-02-27 14:03:33,804 INFO  [com.comqi.ngrt.recaptcha.RecaptchaLoginForm] (executor-thread-67) Calling action method from parent class
2024-02-27 14:03:33,804 INFO  [com.comqi.ngrt.idp.EngageUserStorageProvider] (executor-thread-67) Validating user comqitesting@gmail.com against EnGage
2024-02-27 14:03:33,904 INFO  [com.comqi.ngrt.idp.EngageUserStorageProvider] (executor-thread-67) Login Response: {"request":"authenticate()","diagnostics":"Invalid Credentials","errorMessage":"Unauthorized","messageCode":"badcredentials","who":"anonymousUser"} Status: 401
2024-02-27 14:03:33,904 ERROR [com.comqi.ngrt.idp.EngageUserStorageProvider] (executor-thread-67) Login failed: {"request":"authenticate()","diagnostics":"Invalid Credentials","errorMessage":"Unauthorized","messageCode":"badcredentials","who":"anonymousUser"}
2024-02-27 14:03:33,904 ERROR [com.comqi.ngrt.idp.EngageUserStorageProvider] (executor-thread-67) Request to verify credentials for userId f:c67645c8-bc66-4a98-a860-068a652f0cae:comqitesting@gmail.com failed with response status 401: jakarta.ws.rs.WebApplicationException: Could not login
        at com.comqi.ngrt.idp.httpclient.HttpClientConnector.getCredentialData(HttpClientConnector.java:86)
        at com.comqi.ngrt.idp.EngageUserStorageProvider.isValid(EngageUserStorageProvider.java:174)
        at org.keycloak.credential.LegacyUserCredentialManager.lambda$validate$11(LegacyUserCredentialManager.java:255)
        at java.base/java.util.Collection.removeIf(Collection.java:576)
        at org.keycloak.credential.LegacyUserCredentialManager.validate(LegacyUserCredentialManager.java:255)
        at org.keycloak.credential.LegacyUserCredentialManager.isValid(LegacyUserCredentialManager.java:71)
        at org.keycloak.models.SubjectCredentialManager.isValid(SubjectCredentialManager.java:45)
        at org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator.validatePassword(AbstractUsernameFormAuthenticator.java:229)
        at org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator.validateUserAndPassword(AbstractUsernameFormAuthenticator.java:150)
        at org.keycloak.authentication.authenticators.browser.UsernamePasswordForm.validateForm(UsernamePasswordForm.java:55)
        at org.keycloak.authentication.authenticators.browser.UsernamePasswordForm.action(UsernamePasswordForm.java:48)
        at com.comqi.ngrt.recaptcha.RecaptchaLoginForm.action(RecaptchaLoginForm.java:121)
        at org.keycloak.authentication.DefaultAuthenticationFlow.processAction(DefaultAuthenticationFlow.java:154)
        at org.keycloak.authentication.AuthenticationProcessor.authenticationAction(AuthenticationProcessor.java:988)
        at org.keycloak.services.resources.LoginActionsService.processFlow(LoginActionsService.java:362)
        at org.keycloak.services.resources.LoginActionsService.processAuthentication(LoginActionsService.java:333)
        at org.keycloak.services.resources.LoginActionsService.authenticate(LoginActionsService.java:325)
        at org.keycloak.services.resources.LoginActionsService.authenticateForm(LoginActionsService.java:390)
        at org.keycloak.services.resources.LoginActionsService$quarkusrestinvoker$authenticateForm_32b8e198ac3110abd1d5774e83a4cf87858129f4.invoke(Unknown Source)
        at org.jboss.resteasy.reactive.server.handlers.InvocationHandler.handle(InvocationHandler.java:29)
        at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:141)
        at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:145)
        at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:576)
        at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
        at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
        at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:833)

2024-02-27 14:03:33,969 WARN  [org.keycloak.events] (executor-thread-67) type=LOGIN_ERROR, realmId=2fff2745-f078-4d14-91ca-44c21611d58a, clientId=engage-gui-client-home-idp, userId=f:c67645c8-bc66-4a98-a860-068a652f0cae:comqitesting@gmail.com, ipAddress=10.212.134.154, error=invalid_user_credentials, auth_method=auth_method, auth_type=code, redirect_uri=http://db-sandbox.corp.enqii.com:6080/, code_id=5e2091e4-5857-4d0d-b786-3b7125d35491
2024-02-27 14:05:08,536 INFO  [com.comqi.ngrt.recaptcha.RecaptchaLoginForm] (executor-thread-73) Calling action method from parent class
2024-02-27 14:05:08,537 INFO  [com.comqi.ngrt.idp.EngageUserStorageProvider] (executor-thread-73) Validating user comqitesting@gmail.com against EnGage
2024-02-27 14:05:08,641 INFO  [com.comqi.ngrt.idp.EngageUserStorageProvider] (executor-thread-73) Login Response: {"request":"authenticate()","diagnostics":"Invalid Credentials","errorMessage":"Unauthorized","messageCode":"badcredentials","who":"anonymousUser"} Status: 401

Hi Bryce,
we do have that authenticator (the one that inspired that public example) in use with custom user providers.
Regarding the issue of recaptcha not being activated on return of a failed authentication, we fixed that long time ago but that did not made its way back to the public example. I’ll check if someone can port that fix.
Is the authenticator working OK with a local user? Is your custom user repo provider working with the default username/password form provider?

The custom user repo provider works with the default username/password form provider. I don’t have a local user ATM to test the authenticator, but I will make one and try that to make sure the authenticator is working properly

I can confirm that the plugin works with a local user