instipod / DuoUniversalKeycloakAuthenticator

Keycloak Authenticator for Duo's new Universal Prompt
GNU General Public License v3.0
50 stars 16 forks source link

Conditional 2FA - Not Working #1

Closed jeremywillans closed 3 years ago

jeremywillans commented 3 years ago

Firstly, thanks for this plugin!

It seems to work great... except when used in a conditional forwarder scenario.

I have configured an Authentication Flow to support 2FA using WebAuthN and Duo, but for some reason it just throws me back to the "Sign in with Security Key" screen rather than processing the flow.

WebAuthn Guide Keycloak 15.0.2 in Docker

Also, the "display text" does not appear to be showing on the prompt, I am getting placeholders instead 🤔

image

instipod commented 3 years ago

Hi, thanks for the report!

Both my test instance and my production instance don't use a split flow like that at the moment so it is likely broken as you reported. I'll get a split flow setup on my test instance using the latest Keycloak this weekend and see if I can get the problem solved!

instipod commented 3 years ago

I wasn't able to find a ton of developer documentation on how the "Try another way" system works. Based on my testing, it looks like the request to select another authenticator is a POST request, and even specifying the same fields via GET does not select the authenticator, instead starts at the default authenticator.

The Duo Universal callback is always a GET request which poses a problem. As a workaround, this extension will now add an additional endpoint to the Keycloak server that will accept the GET callback from Duo, and redirect the browser to send the data to the normal authentication flow over POST.

I'm not super happy with this solution as it does add an additional redirect to the chain, which increases the overall authentication time (especially on slow connections), but I cannot think of a better solution for now.

I've released 1.0.2 as a prerelease if you want to download it and give it a try to make sure it works correctly in your authentication flow. https://github.com/instipod/DuoUniversalKeycloakAuthenticator/releases/tag/1.0.2-pr

instipod commented 3 years ago

As for the display text, it looks like that comes from the theme localization file.

Adding the following to /opt/jboss/keycloak/themes/base/login/messages/messages_en.properties file:

duo-universal-display-name=Duo Push duo-universal-help-text=Send a Duo Push to your devices

results in the following being displayed:

Screen Shot 2021-09-10 at 6 31 14 PM
jeremywillans commented 3 years ago

@instipod This has worked, thanks!

joeknock90 commented 2 years ago

I wasn't sure if I should leave a new issue or just post to this one. I am currently experiencing this issue with keycloak 19.0.1

I figure it's at least similar as Duo works if it's the ONLY second factor provider, but not for split auth flows. Here's some pictures!

Works! image

Doesn't work =[ image

Here's some logs for when I try.

keycloak-keycloak-1  | 2022-09-13 21:26:35,829 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (executor-thread-320) Uncaught server error: java.lang.NoSuchMethodError: 'org.jboss.resteasy.spi.ResteasyUriInfo org.jboss.resteasy.spi.HttpRequest.getUri()'
keycloak-keycloak-1  |  at com.instipod.duouniversal.DuoUniversalAuthenticator.getRedirectUrlShim(DuoUniversalAuthenticator.java:42)
keycloak-keycloak-1  |  at com.instipod.duouniversal.DuoUniversalAuthenticator.getRedirectUrl(DuoUniversalAuthenticator.java:31)
keycloak-keycloak-1  |  at com.instipod.duouniversal.DuoUniversalAuthenticator.startDuoProcess(DuoUniversalAuthenticator.java:193)
keycloak-keycloak-1  |  at com.instipod.duouniversal.DuoUniversalAuthenticator.authenticate(DuoUniversalAuthenticator.java:133)
keycloak-keycloak-1  |  at org.keycloak.authentication.DefaultAuthenticationFlow.processSingleFlowExecutionModel(DefaultAuthenticationFlow.java:446)
keycloak-keycloak-1  |  at org.keycloak.authentication.DefaultAuthenticationFlow.processAction(DefaultAuthenticationFlow.java:122)
keycloak-keycloak-1  |  at org.keycloak.authentication.AuthenticationProcessor.authenticationAction(AuthenticationProcessor.java:977)
keycloak-keycloak-1  |  at org.keycloak.services.resources.LoginActionsService.processFlow(LoginActionsService.java:314)
keycloak-keycloak-1  |  at org.keycloak.services.resources.LoginActionsService.processAuthentication(LoginActionsService.java:285)
keycloak-keycloak-1  |  at org.keycloak.services.resources.LoginActionsService.authenticate(LoginActionsService.java:277)
keycloak-keycloak-1  |  at org.keycloak.services.resources.LoginActionsService.authenticateForm(LoginActionsService.java:342)
keycloak-keycloak-1  |  at jdk.internal.reflect.GeneratedMethodAccessor648.invoke(Unknown Source)
keycloak-keycloak-1  |  at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
keycloak-keycloak-1  |  at java.base/java.lang.reflect.Method.invoke(Method.java:566)
keycloak-keycloak-1  |  at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:170)
keycloak-keycloak-1  |  at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
keycloak-keycloak-1  |  at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:660)
keycloak-keycloak-1  |  at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:524)
keycloak-keycloak-1  |  at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:474)
keycloak-keycloak-1  |  at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
keycloak-keycloak-1  |  at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:476)
keycloak-keycloak-1  |  at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:434)
keycloak-keycloak-1  |  at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:192)
keycloak-keycloak-1  |  at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:141)
keycloak-keycloak-1  |  at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:32)
keycloak-keycloak-1  |  at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:492)
keycloak-keycloak-1  |  at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:261)
keycloak-keycloak-1  |  at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:161)
keycloak-keycloak-1  |  at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
keycloak-keycloak-1  |  at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:164)
keycloak-keycloak-1  |  at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:247)
keycloak-keycloak-1  |  at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73)
keycloak-keycloak-1  |  at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:151)
keycloak-keycloak-1  |  at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:82)
keycloak-keycloak-1  |  at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:42)
keycloak-keycloak-1  |  at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1212)
keycloak-keycloak-1  |  at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:163)
keycloak-keycloak-1  |  at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
keycloak-keycloak-1  |  at io.quarkus.vertx.http.runtime.StaticResourcesRecorder$2.handle(StaticResourcesRecorder.java:67)
keycloak-keycloak-1  |  at io.quarkus.vertx.http.runtime.StaticResourcesRecorder$2.handle(StaticResourcesRecorder.java:55)
keycloak-keycloak-1  |  at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1212)
keycloak-keycloak-1  |  at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:163)
keycloak-keycloak-1  |  at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
keycloak-keycloak-1  |  at io.quarkus.vertx.http.runtime.VertxHttpRecorder$5.handle(VertxHttpRecorder.java:380)
keycloak-keycloak-1  |  at io.quarkus.vertx.http.runtime.VertxHttpRecorder$5.handle(VertxHttpRecorder.java:358)
keycloak-keycloak-1  |  at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1212)
keycloak-keycloak-1  |  at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:163)
keycloak-keycloak-1  |  at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
keycloak-keycloak-1  |  at org.keycloak.quarkus.runtime.integration.web.QuarkusRequestFilter.lambda$createBlockingHandler$1(QuarkusRequestFilter.java:90)
keycloak-keycloak-1  |  at io.vertx.core.impl.ContextImpl.lambda$null$0(ContextImpl.java:159)
keycloak-keycloak-1  |  at io.vertx.core.impl.AbstractContext.dispatch(AbstractContext.java:100)
keycloak-keycloak-1  |  at io.vertx.core.impl.ContextImpl.lambda$executeBlocking$1(ContextImpl.java:157)
keycloak-keycloak-1  |  at io.quarkus.vertx.core.runtime.VertxCoreRecorder$13.runWith(VertxCoreRecorder.java:545)
keycloak-keycloak-1  |  at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
keycloak-keycloak-1  |  at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
keycloak-keycloak-1  |  at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
keycloak-keycloak-1  |  at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
keycloak-keycloak-1  |  at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
keycloak-keycloak-1  |  at java.base/java.lang.Thread.run(Thread.java:829)

I can 100% open a new issue if that's better also.

instipod commented 2 years ago

It would probably be best to open a new issue since this one is already closed.