Issue Denying access with Authentication Flows
See original GitHub issueDescribe the bug
Hey there.
I’m currently trying to deny access to users that are not associated with a specific client role. Following the documentation on https://www.keycloak.org/docs/latest/server_admin/index.html#explicitly-deny-allow-access-in-conditional-flows the flow that I’m trying to use is the following (basically a copy of Direct Grant changing just the OTP conditional):
Username Validation [REQUIRED]
Password [REQUIRED]
Check Role [CONDITIONAL]
Condition - User Role(User MUST HAVE ROLE) [REQUIRED]
Deny Access [REQUIRED]
When I try to authenticate a user that has the role, everything works as expected, however when I try to authenticate a user that doesn’t have the role associated I get an unknown_error and the logs are as follows:
2022-07-27 12:03:59,948 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (executor-thread-54) Uncaught server error: java.lang.IllegalArgumentException: RESTEASY003715: path was null
at org.jboss.resteasy.specimpl.ResteasyUriBuilderImpl.path(ResteasyUriBuilderImpl.java:382)
at org.keycloak.authentication.AuthenticationProcessor$Result.getActionUrl(AuthenticationProcessor.java:562)
at org.keycloak.authentication.AuthenticationProcessor$Result.form(AuthenticationProcessor.java:536)
at org.keycloak.authentication.authenticators.access.DenyAccessAuthenticator.authenticate(DenyAccessAuthenticator.java:49)
at org.keycloak.authentication.DefaultAuthenticationFlow.processSingleFlowExecutionModel(DefaultAuthenticationFlow.java:460)
at org.keycloak.authentication.DefaultAuthenticationFlow.processFlow(DefaultAuthenticationFlow.java:264)
at org.keycloak.authentication.DefaultAuthenticationFlow.processSingleFlowExecutionModel(DefaultAuthenticationFlow.java:395)
at org.keycloak.authentication.DefaultAuthenticationFlow.processFlow(DefaultAuthenticationFlow.java:264)
at org.keycloak.authentication.AuthenticationProcessor.authenticateOnly(AuthenticationProcessor.java:1030)
at org.keycloak.protocol.oidc.endpoints.TokenEndpoint.resourceOwnerPasswordCredentialsGrant(TokenEndpoint.java:603)
at org.keycloak.protocol.oidc.endpoints.TokenEndpoint.processGrantRequest(TokenEndpoint.java:200)
at jdk.internal.reflect.GeneratedMethodAccessor343.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:170)
at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:130)
at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:660)
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:524)
at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:474)
at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:476)
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:434)
at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:192)
at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:152)
at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:183)
at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:141)
at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:32)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:492)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:261)
at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:161)
at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:364)
at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:164)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:247)
at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73)
at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:151)
at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:82)
at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.handle(VertxRequestHandler.java:42)
at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1212)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:163)
at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
at io.quarkus.vertx.http.runtime.StaticResourcesRecorder$2.handle(StaticResourcesRecorder.java:67)
at io.quarkus.vertx.http.runtime.StaticResourcesRecorder$2.handle(StaticResourcesRecorder.java:55)
at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1212)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:163)
at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
at io.quarkus.vertx.http.runtime.VertxHttpRecorder$5.handle(VertxHttpRecorder.java:380)
at io.quarkus.vertx.http.runtime.VertxHttpRecorder$5.handle(VertxHttpRecorder.java:358)
at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1212)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:163)
at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:141)
at org.keycloak.quarkus.runtime.integration.web.QuarkusRequestFilter.lambda$createBlockingHandler$1(QuarkusRequestFilter.java:71)
at io.vertx.core.impl.ContextImpl.lambda$null$0(ContextImpl.java:159)
at io.vertx.core.impl.AbstractContext.dispatch(AbstractContext.java:100)
at io.vertx.core.impl.ContextImpl.lambda$executeBlocking$1(ContextImpl.java:157)
at io.quarkus.vertx.core.runtime.VertxCoreRecorder$13.runWith(VertxCoreRecorder.java:543)
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
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:829)
2022-07-27 12:03:59,951 WARN [io.agroal.pool] (executor-thread-54) Datasource '<default>': JDBC resources leaked: 2 ResultSet(s) and 2 Statement(s)
Is this a bug or am I missing something?
Version
18.0.2
Expected behavior
Getting an unauthorized error when trying to authenticate a user that doesn’t have the required role.
Actual behavior
Getting a server error, with an exception on the logs.
How to Reproduce?
- Create a copy of direct grant authentication flow, replace the OTP step with a conditional that checks if the user doesn’t have the role and then denies access.
- Add this flow to a client
- create a user without the specified role and authenticate it.
Anything else?
No response
Issue Analytics
- State:
- Created a year ago
- Reactions:1
- Comments:6 (2 by maintainers)
Top Related StackOverflow Question
@lexcao Oh sorry, you are correct, I did forgot to mention the authentication method. I thought I mentioned it somewhere but what I said was that I copied the Direct Grant Authentication Flow, my bad.
Also, thank you very much for the in depth explanation, that is way out of my league, but glad to know this is a bug and not something that I was missing (also good to know that it works on browser).
Hi @filipetavares I reproduced this from my local, it should be an issue, I think. And I want to say, you forgot to mention what authentication method you’re using. Finally, I found you are using
Direct Grant Flowfrom the given log. (TokenEndpoint.resourceOwnerPasswordCredentialsGrant) It works normally when I useBrowser Flowfrom my side.Here is my investigation:
From the log, I found the
path was nullhappens here.https://github.com/keycloak/keycloak/blob/8ed9ce29d159d10728ada8da708dab111921b024/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java#L564
And for the direct grant flow, the
AuthenticationProcessor.this.flowPathis not initialized.https://github.com/keycloak/keycloak/blob/8ed9ce29d159d10728ada8da708dab111921b024/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java#L594-L605
IMO, it makes sense for direct grant flow not initializing the flow path, if not it could mislead the user to the front page when an error happens in token API.
I think it should respond the error properly with the related content type (like JSON) when calling token API with direct grant.
How to fix it? I have some ideas and need the Keycloak team to check.
Proposal 1
Modify the
DenyAccessAuthenticatorto support JSON response type.https://github.com/keycloak/keycloak/blob/650f3a8367ea5d5f818d36b4c5fecf50d0576154/services/src/main/java/org/keycloak/authentication/authenticators/access/DenyAccessAuthenticator.java#L48-L52
Proposal 2
Modify the challenge of direct grant flow, which makes it respond JSON instead of HTML.
https://github.com/keycloak/keycloak/blob/650f3a8367ea5d5f818d36b4c5fecf50d0576154/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java#L605-L611